{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "raw_mimetype": "text/restructuredtext"
   },
   "source": [
    ".. _nb_kktpm:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Karush Kuhn Tucker Proximity Measure (KKTPM)\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In 2016, Deb and Abouhawwash proposed Karush Kuhn Tucker Proximity Measure (KKTPM) <cite data-cite=\"kktpm1\"></cite>, a metric that can measure how close a point is from being “an optimum”. The smaller the metric, the closer the point. This does not require the Pareto front to be known, but the gradient information needs to be approximated.\n",
    "Their metric applies to both single objective and multi-objective optimization problems. \n",
    "\n",
    "In a single objective problem, the metric shows how close a point is from being a “local optimum”, while in multi-objective problems, the metric shows how close a point is from being a “local Pareto point”. Exact calculations of KKTPM for each point requires solving a whole optimization problem, which is extremely time-consuming. To avoid this problem, the authors of the original work again proposed several approximations to the true KKTPM, namely Direct KKTPM, Projected KKTPM, Adjusted KKTPM, and Approximate KKTPM. Approximate KKTPM is simply the average of the former three and is what we call simply “KKTPM”. Moreover, they were able to show that Approximate KKTPM is reliable and can be used in place of the exact one <cite data-cite=\"kktpm2\"></cite>."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div style=\"text-align: center;\">\n",
    "    <img src=\"https://github.com/anyoptimization/pymoo-data/blob/main/docs/images/kktpm.png?raw=true\" width=\"350\">\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us now see how to use pymoo to calculate the KKTPM for point:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from pymoo.constraints.from_bounds import ConstraintsFromBounds\n",
    "from pymoo.gradient.automatic import AutomaticDifferentiation\n",
    "from pymoo.problems import get_problem\n",
    "\n",
    "problem = AutomaticDifferentiation(ConstraintsFromBounds(get_problem(\"zdt1\", n_var=30)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For instance, the code below calculates the KKTPM metric for randomly sampled points for the given an example;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from pymoo.indicators.kktpm import KKTPM\n",
    "from pymoo.operators.sampling.rnd import FloatRandomSampling\n",
    "\n",
    "X = FloatRandomSampling().do(problem, 100).get(\"X\")\n",
    "kktpm = KKTPM().calc(X, problem)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Moreover, a whole run of a genetic algorithm can be analyzed by storing each generation's history and then calculating the KKTPM metric for each of the points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from pymoo.algorithms.moo.nsga2 import NSGA2\n",
    "from pymoo.problems import get_problem\n",
    "from pymoo.optimize import minimize\n",
    "from pymoo.visualization.scatter import Scatter\n",
    "from pymoo.core.evaluator import Evaluator\n",
    "\n",
    "\n",
    "algorithm = NSGA2(pop_size=100, eliminate_duplicates=True)\n",
    "\n",
    "# make sure each evaluation also has the derivatives - necessary for KKTPM\n",
    "evaluator = Evaluator(evaluate_values_of=[\"F\", \"G\", \"dF\", \"dG\"])\n",
    "\n",
    "res = minimize(problem,\n",
    "               algorithm,\n",
    "               ('n_gen', 100),\n",
    "               evaluator=evaluator,\n",
    "               seed=1,\n",
    "               save_history=True,\n",
    "               verbose=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "gen, _min, _median, _max = [], [], [], []\n",
    "\n",
    "for algorithm in res.history:\n",
    "    if algorithm.n_gen % 5 == 0:\n",
    "        X = algorithm.pop.get(\"X\")\n",
    "        kktpm = KKTPM().calc(X, problem)\n",
    "\n",
    "        gen.append(algorithm.n_gen)\n",
    "        _min.append(kktpm.min())\n",
    "        _median.append(np.median(kktpm))\n",
    "        _max.append(kktpm.max())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.plot(gen, _min, label=\"Min\")\n",
    "plt.plot(gen, _median, label=\"Median\")\n",
    "plt.plot(gen, _max, label=\"Max\")\n",
    "plt.yscale(\"log\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
